package com.wf.gts.remoting.core;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicLong;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MixAll {
  private static final Logger log = LoggerFactory.getLogger(MixAll.class);

  public static final String ROCKETMQ_HOME_ENV = "ROCKETMQ_HOME";
  public static final String ROCKETMQ_HOME_PROPERTY = "rocketmq.home.dir";
  public static final String NAMESRV_ADDR_ENV = "NAMESRV_ADDR";
  public static final String NAMESRV_ADDR_PROPERTY = "rocketmq.namesrv.addr";
  public static final String MESSAGE_COMPRESS_LEVEL = "rocketmq.message.compressLevel";
  public static final List<String> LOCAL_INET_ADDRESS = getLocalInetAddress();
  public static final String LOCALHOST = localhost();
  public static final String DEFAULT_CHARSET = "UTF-8";
  public static final long MASTER_ID = 0L;
  public static final long CURRENT_JVM_PID = getPID();


  public static long getPID() {
      String processName = java.lang.management.ManagementFactory.getRuntimeMXBean().getName();
      if (processName != null && processName.length() > 0) {
          try {
              return Long.parseLong(processName.split("@")[0]);
          } catch (Exception e) {
              return 0;
          }
      }

      return 0;
  }

  public static void string2File(final String str, final String fileName) throws IOException {

      String tmpFile = fileName + ".tmp";
      string2FileNotSafe(str, tmpFile);

      String bakFile = fileName + ".bak";
      String prevContent = file2String(fileName);
      if (prevContent != null) {
          string2FileNotSafe(prevContent, bakFile);
      }

      File file = new File(fileName);
      file.delete();

      file = new File(tmpFile);
      file.renameTo(new File(fileName));
  }

  public static void string2FileNotSafe(final String str, final String fileName) throws IOException {
      File file = new File(fileName);
      File fileParent = file.getParentFile();
      if (fileParent != null) {
          fileParent.mkdirs();
      }
      FileWriter fileWriter = null;

      try {
          fileWriter = new FileWriter(file);
          fileWriter.write(str);
      } catch (IOException e) {
          throw e;
      } finally {
          if (fileWriter != null) {
              fileWriter.close();
          }
      }
  }

  public static String file2String(final String fileName) throws IOException {
      File file = new File(fileName);
      return file2String(file);
  }

  public static String file2String(final File file) throws IOException {
      if (file.exists()) {
          byte[] data = new byte[(int) file.length()];
          boolean result;

          FileInputStream inputStream = null;
          try {
              inputStream = new FileInputStream(file);
              int len = inputStream.read(data);
              result = len == data.length;
          } finally {
              if (inputStream != null) {
                  inputStream.close();
              }
          }

          if (result) {
              return new String(data);
          }
      }
      return null;
  }

  public static String file2String(final URL url) {
      InputStream in = null;
      try {
          URLConnection urlConnection = url.openConnection();
          urlConnection.setUseCaches(false);
          in = urlConnection.getInputStream();
          int len = in.available();
          byte[] data = new byte[len];
          in.read(data, 0, len);
          return new String(data, "UTF-8");
      } catch (Exception ignored) {
      } finally {
          if (null != in) {
              try {
                  in.close();
              } catch (IOException ignored) {
              }
          }
      }

      return null;
  }

 
  
  public static String properties2String(final Properties properties) {
      StringBuilder sb = new StringBuilder();
      for (Map.Entry<Object, Object> entry : properties.entrySet()) {
          if (entry.getValue() != null) {
              sb.append(entry.getKey().toString() + "=" + entry.getValue().toString() + "\n");
          }
      }
      return sb.toString();
  }

  public static Properties string2Properties(final String str) {
      Properties properties = new Properties();
      try {
          InputStream in = new ByteArrayInputStream(str.getBytes(DEFAULT_CHARSET));
          properties.load(in);
      } catch (Exception e) {
          log.error("Failed to handle properties", e);
          return null;
      }

      return properties;
  }

  public static Properties object2Properties(final Object object) {
      Properties properties = new Properties();

      Field[] fields = object.getClass().getDeclaredFields();
      for (Field field : fields) {
          if (!Modifier.isStatic(field.getModifiers())) {
              String name = field.getName();
              if (!name.startsWith("this")) {
                  Object value = null;
                  try {
                      field.setAccessible(true);
                      value = field.get(object);
                  } catch (IllegalAccessException e) {
                      log.error("Failed to handle properties", e);
                  }

                  if (value != null) {
                      properties.setProperty(name, value.toString());
                  }
              }
          }
      }

      return properties;
  }

  public static void properties2Object(final Properties p, final Object object) {
      Method[] methods = object.getClass().getMethods();
      for (Method method : methods) {
          String mn = method.getName();
          if (mn.startsWith("set")) {
              try {
                  String tmp = mn.substring(4);
                  String first = mn.substring(3, 4);

                  String key = first.toLowerCase() + tmp;
                  String property = p.getProperty(key);
                  if (property != null) {
                      Class<?>[] pt = method.getParameterTypes();
                      if (pt != null && pt.length > 0) {
                          String cn = pt[0].getSimpleName();
                          Object arg = null;
                          if (cn.equals("int") || cn.equals("Integer")) {
                              arg = Integer.parseInt(property);
                          } else if (cn.equals("long") || cn.equals("Long")) {
                              arg = Long.parseLong(property);
                          } else if (cn.equals("double") || cn.equals("Double")) {
                              arg = Double.parseDouble(property);
                          } else if (cn.equals("boolean") || cn.equals("Boolean")) {
                              arg = Boolean.parseBoolean(property);
                          } else if (cn.equals("float") || cn.equals("Float")) {
                              arg = Float.parseFloat(property);
                          } else if (cn.equals("String")) {
                              arg = property;
                          } else {
                              continue;
                          }
                          method.invoke(object, arg);
                      }
                  }
              } catch (Throwable ignored) {
              }
          }
      }
  }

  public static boolean isPropertiesEqual(final Properties p1, final Properties p2) {
      return p1.equals(p2);
  }

  public static List<String> getLocalInetAddress() {
      List<String> inetAddressList = new ArrayList<String>();
      try {
          Enumeration<NetworkInterface> enumeration = NetworkInterface.getNetworkInterfaces();
          while (enumeration.hasMoreElements()) {
              NetworkInterface networkInterface = enumeration.nextElement();
              Enumeration<InetAddress> addrs = networkInterface.getInetAddresses();
              while (addrs.hasMoreElements()) {
                  inetAddressList.add(addrs.nextElement().getHostAddress());
              }
          }
      } catch (SocketException e) {
          throw new RuntimeException("get local inet address fail", e);
      }

      return inetAddressList;
  }

  private static String localhost() {
      try {
          return InetAddress.getLocalHost().getHostAddress();
      } catch (Throwable e) {
          try {
              String candidatesHost = getLocalhostByNetworkInterface();
              if (candidatesHost != null)
                  return candidatesHost;

          } catch (Exception ignored) {
          }

          throw new RuntimeException("InetAddress java.net.InetAddress.getLocalHost() throws UnknownHostException", e);
      }
  }

  //Reverse logic comparing to RemotingUtil method, consider refactor in RocketMQ 5.0
  public static String getLocalhostByNetworkInterface() throws SocketException {
      List<String> candidatesHost = new ArrayList<String>();
      Enumeration<NetworkInterface> enumeration = NetworkInterface.getNetworkInterfaces();

      while (enumeration.hasMoreElements()) {
          NetworkInterface networkInterface = enumeration.nextElement();
          // Workaround for docker0 bridge
          if ("docker0".equals(networkInterface.getName()) || !networkInterface.isUp()) {
              continue;
          }
          Enumeration<InetAddress> addrs = networkInterface.getInetAddresses();
          while (addrs.hasMoreElements()) {
              InetAddress address = addrs.nextElement();
              if (address.isLoopbackAddress()) {
                  continue;
              }
              //ip4 highter priority
              if (address instanceof Inet6Address) {
                  candidatesHost.add(address.getHostAddress());
                  continue;
              }
              return address.getHostAddress();
          }
      }

      if (!candidatesHost.isEmpty()) {
          return candidatesHost.get(0);
      }
      return null;
  }

  public static boolean compareAndIncreaseOnly(final AtomicLong target, final long value) {
      long prev = target.get();
      while (value > prev) {
          boolean updated = target.compareAndSet(prev, value);
          if (updated)
              return true;

          prev = target.get();
      }

      return false;
  }

  public static String humanReadableByteCount(long bytes, boolean si) {
      int unit = si ? 1000 : 1024;
      if (bytes < unit)
          return bytes + " B";
      int exp = (int) (Math.log(bytes) / Math.log(unit));
      String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i");
      return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
  }

}
